
from google.appengine.ext import db
from google.appengine.api import memcache as gae_memcache

import hashlib
from datetime import datetime, timedelta
import pickle

CACHE_TIME = 3600 * 24 * 10

class Cache(db.Model):
    cache_key = db.StringProperty(required=True) # max 500
    created = db.DateTimeProperty(auto_now_add=True, indexed=False)
    data = db.BlobProperty()
    timeout = db.DateTimeProperty() # set but unused currently

    @classmethod
    def some_expired(cls):
        return Cache.all().filter('timeout <', datetime.now()).count(1)

    @classmethod
    def cleanup(cls):
        if cls.some_expired():
            db.delete(Cache.all().filter('timeout <', datetime.now()).fetch(500))


class CustomCache(object):
    def __init__(self, expires=CACHE_TIME, namespace='kawartha-growers', use_store=False): # not using cache store for keg
        self.expires = expires
        self.namespace = namespace
        self.use_store = use_store
    
    # need to hash b/c some keys are too long to put in 500 byte StringProperty
    def _store_key(self, k):
        # to preserve cache & not break on long keys
        # memcache max is 250 i think, though GAE docs don't mention this..
        if len(k) > 200:
            return hashlib.sha1(k).hexdigest()
        else:
            return str(k)
    
    def _get_from_store(self, k):
        data = None
        cache = db.GqlQuery("SELECT * FROM Cache WHERE cache_key = :1", k).get()
        if cache:
            # reset the expires timeout, not necessary for keg app
            #cache.timeout = datetime.now() + timedelta(0, self.expires)
            #cache.put()
            data = pickle.loads(cache.data)
            self.set(k, data, use_store=False) # put back in memory cache
        return data
    
    def _save_to_store(self, k, v, expires):
        timeout = datetime.now() + timedelta(0, expires)
        cache = Cache(cache_key=k, data=pickle.dumps(v), timeout=timeout) # not using namespace in datastore..
        cache.put()
    
    def set(self, k, v, expires=None, use_store=True):
        key = self._store_key(k)
        if not expires:
            expires = self.expires
        gae_memcache.set(key, v, expires, namespace=self.namespace)
        if self.use_store and use_store:
            # should not already exist or this will throw an error..
            self._save_to_store(key, v, expires)
        
    def get(self, k):
        key = self._store_key(k)
        result = gae_memcache.get(key, namespace=self.namespace)
        if not result and self.use_store:
            result = self._get_from_store(key)
        return result
        
    def get_multi(self, ks, use_store=True, as_dict=False):
        keys = [self._store_key(k) for k in ks]
        data_dict = gae_memcache.get_multi(keys, namespace=self.namespace)
        data = None
        if as_dict:
            for k in keys:
                if not data_dict.has_key(k) and self.use_store and use_store:
                    item = self._get_from_store(k)
                    if item:
                        data_dict[k] = item
            return data_dict
        else:
            # now order them correctly
            data = []
            for k in keys:
                if data_dict.has_key(k):
                    data.append(data_dict[k])
                elif self.use_store and use_store:
                    item = self._get_from_store(k)
                    if item:
                        data.append(item)
            return data
    
    def delete_multi(self, ks, use_store=True):
        keys = [self._store_key(k) for k in ks]
        if keys:
            result = gae_memcache.delete_multi(keys, namespace=self.namespace)
            if self.use_store and use_store:
                assert False, 'deleting from data store not supported yet..'
            return result
        
    def delete(self, k, use_store=True):
        result = gae_memcache.delete(self._store_key(k), namespace=self.namespace)
        if self.use_store and use_store:
            assert False, 'deleting from data store not supported yet..'
        return result

    def incr(self, k, delta=1, namespace=None, initial_value=None):
        key = self._store_key(k)
        return gae_memcache.incr(k, delta=delta, 
                                 namespace=namespace or self.namespace, initial_value=initial_value)
    def decr(self, k, delta=1, namespace=None, initial_value=None):
        key = self._store_key(k)
        return gae_memcache.decr(k, delta=delta, 
                                 namespace=namespace or self.namespace, initial_value=initial_value)


memcache = CustomCache()